﻿package org.liveboardgames.common.dictionary.enum.types
{

    import flash.utils.getQualifiedClassName;
    
    import org.liveboardgames.common.dictionary.enum.core.IComparable;
    import org.liveboardgames.common.dictionary.enum.errors.EnumError;

    /**
     * This class implements enumeration types in Actionscript.
     *
     * <p>In Actionscript, you typically create enumerations by declaring
     * static constants in a subclass of <code>Object</code>. The advantage is
     * that it is fast and easy to deploy. Nevertheless, there is a lack of type
     * checking when you do like this.<br/>
     * Creating a subclass of <code>Enum</code> solves the problem without
     * adding complexity. You create your constants by calling the static
     * method <code>createEnum()</code>.</p>
     *
     * @example The following code shows the same enumeration class
     * created with the Enum class, and the classic thinking.
     * <listing version="3.0">
     *
     * public class City
     * {
     * 		public static const PARIS:String = "Paris";
     * 		public static const LONDON:String = "London";
     * 		public static const ROMA:String = "Roma";
     * }
     *
     * public class City extends Enum
     * {
     * 		public static const PARIS:City = createEnum(City, "Paris");
     * 		public static const LONDON:City = createEnum(City, "London");
     * 		public static const ROMA:City = createEnum(City, "Roma");
     * }
     *
     * </listing>
     *
     */
    public class Enum implements IComparable
    {

        //------------------------------
        //
        // Class members
        //
        //------------------------------

        /**
         * This method has to be used in every <code>Enum</code>
         * subclass when declaring the enumeration constants.
         *
         * @example The following code shows how to use this method to declare
         * enum constants :
         * <listing version="3.0">
         *
         * // The most basic declaration
         * public static const PARIS:City = createEnum(City, "Paris");
         * // You could also have declared a more complex constant like :
         * public static const PARIS:City = createEnum(City, "Paris", 0, "France", "Europe");
         *
         * </listing>
         *
         * @param enumType The type of the enumeration constant.
         * Typically, this is the same type as the constant and
         * the subclass types.
         * @param name The explicit name of the enumeration constant.
         * @param ordinal The order index you want this enumeration
         * constant to take.
         * Pass <code>0</code> to ignore it.
         * @param ...args Additionnal parameters that you want this
         * enumeration constant to take. All other enumeration
         * constants should match the same number and type of arguments.
         * @return An enumeration constant of the specified type.
         */
        protected static function createEnum(enumType:Class, name:String, ordinal:int = 0, ... args):*
        {
            var enum:Enum = new enumType() as Enum;
            enum.__initialize(enumType, name, ordinal, args);
            return enum as enumType;
        }

        //------------------------------
        //
        // Properties
        //
        //------------------------------

        /**
         * The additionnal arguments that where provided when
         * declaring the <code>Enum</code> subclass constants.
         */
        protected var additionnalArguments:Array;

        /**
         * @private
         */
        private var _enumType:Class;

        /**
         * The type of the <code>Enum</code> constant, as
         * it was specified at <code>Enum</code> constant
         * declaration time.
         */
        public function get enumType():Class
        {
            return _enumType;
        }

        /**
         * @private
         */
        private var _name:String;

        /**
         * The explicit name of the <code>Enum</code> constant, as
         * it was specified at <code>Enum</code> constant
         * declaration time.
         */
        public function get name():String
        {
            return _name;
        }

        /**
         * @private
         */
        private var _ordinal:int;

        /**
         * The order in which the <code>Enum</code> subclass
         * constants where decided to be ordered.
         *
         * <p>This property can be omitted when declaring
         * constants if you have no use of
         * the order of the constants.</p>
         */
        public function get ordinal():int
        {
            return _ordinal;
        }

        //------------------------------
        //
        // Constructor.
        //
        //------------------------------

        /**
         * @private
         */
        public function Enum()
        {
        }

        //------------------------------
        //
        // Methods
        //
        //------------------------------

        /**
         * @private
         * Initializes this <code>Enum</code> object.
         */
        private function __initialize(enumType:Class, name:String, ordinal:int, ... args):void
        {
            this._enumType = enumType;
            this._name = name;
            this._ordinal = ordinal;
            this.additionnalArguments = args;
        }

        /**
         * Compares the order of this <code>Enum</code> object with
         * the one of the specified <code>Enum</code> object. The order of the
         * <code>Enum</code> object is defined by its <code>ordinal</code> property.
         *
         * <p>Only enumerations of the same type can be compared.</p>
         *
         * @throws EnumError Enumerations of different type cannot be compared.
         * @param enum The enumeration to compare.
         * @return A negative integer, zero, or a positive integer as this <code>Enum</code>
         * is less than, equal to, or greater than the specified <code>Enum</code>.
         */
        public final function compareTo(enum:Enum):int
        {
            if (this._enumType == enum.enumType)
            {
                return this.ordinal - enum.ordinal;
            }
            throw new EnumError(EnumError.COMPARE_ERROR_MESSAGE);
        }

        /**
         * Returns <code>true</code> if the specified object is equal to the <code>Enum</code> object.
         *
         * <p>If you plan to compare enumerations of your <code>Enum</code> subclass, you should consider to
         * override this method.</p>
         * <p>Only enumerations of the same type can be compared.</p>
         *
         * @throws EnumError Enumerations of different type cannot be compared.
         * @param object The other <code>Enum</code> to compare.
         * @return <code>true</code> if the specified object is equal to the <code>Enum</code> object.
         */
        public function equals(object:*):Boolean
        {
            if (object is _enumType)
            {
                var otherEnum:Enum = object as Enum;
                return otherEnum.name == this._name && otherEnum.ordinal == this._ordinal && otherEnum.enumType == this._enumType;
            }
            return false;
        }

        /**
         * A utility function that helps implementing the <code>toString()</code>
         * method in Enum subclasses.
         *
         * <p>It is recommanded to override this method.</p>
         *
         * @param ...args The properties of the <code>Enum</code> class and
         * the properties of the <code>Enum</code> subclass.
         * @return The Enum flag followed by the enumType, then by the
         * string values of the specified properties.
         */
        public final function formatToString(... args):String
        {
            var string:String = "[Enum enumType=\"" + getQualifiedClassName(_enumType) + "\"";
            var property:String;
            var numProperties:uint = args.length;
            for (var i:int = 0; i < numProperties; i++)
            {
                property = args[i];
                string = string.concat(", " + property + "=\"" + this[property].toString() + "\"");
            }
            return string.concat("]");
        }

        /**
         * Returns a string representation of this <code>Enum</code>.
         * @return a string representation of this <code>Enum</code>.
         */
        public function toString():String
        {
            return formatToString("name", "ordinal");
        }

    }

}
